#include "scene.h"
#include "constants.h"

// geometric objects

//#include "Plane.h"
#include "primitives/sphere.h"

// tracers

//#include "SingleSphere.h"
//#include "MultipleObjects.h"
//#include "RayCast.h"

// cameras

//#include "Pinhole.h"

// lights

//#include "Directional.h"

// materials

#include "material.h"

//#include "Matte.h"

// utilities

#include "vector3d.h"
#include "point3d.h"
#include "normal.h"
#include "shaderec.h"
#include "maths.h"

// build functions

//#include "BuildShadedObjects.cpp"


// ------ default constructor

// tracer_ptr is set to NULL because the build functions will always construct the appropriate tracer
// ambient_ptr is set to a default ambient light because this will do for most scenes
// camera_ptr is set to NULL because the build functions will always have to construct a camera
// and set its parameters
#include <iostream>

Scene::Scene(void)
    :  	background_color(black),
        render_res(NULL),
        tracer_ptr(NULL),
        ambient_ptr(new Ambient),
        camera_ptr(NULL)
{}

// ------ destructor
Scene::~Scene(void) {
    if(tracer_ptr) {
        delete tracer_ptr;
        tracer_ptr = NULL;
    }

    if (ambient_ptr) {
        delete ambient_ptr;
        ambient_ptr = NULL;
    }

    if (camera_ptr) {
        delete camera_ptr;
        camera_ptr = NULL;
    }

    if (render_res){
        delete[] render_res;
        render_res = NULL;
    }

    delete_objects();
    delete_lights();

}

// ------ render_scene
// This uses orthographic viewing along the zw axis
/*void
Scene::render_scene(void) const {
    RGBColor	pixel_color = black;
    Ray			ray;
    int 		hres 	= vp.hres;
    int 		vres 	= vp.vres;
    float		s		= vp.s;
    float		zw		= 100.0;				// hardwired in

    ray.d = Vector3D(0, 0, -1);

    for (int r = 0; r < vres; r++){			// up
        for (int c = 0; c < hres; c++) {	// across
            ray.o = Point3D(s * (c - hres / 2.0 + 0.5), s * (r - vres / 2.0 + 0.5), zw);
            pixel_color = tracer_ptr->trace_ray(ray,3);
            display_pixel(r, c, pixel_color);
        }
    }
}
*/

// ------ clamp
RGBColor
Scene::max_to_one(const RGBColor& c) const  {
    float max_value = max(c.r, max(c.g, c.b));

    if (max_value > 1.0)
        return (c / max_value);
    else
        return (c);
}

// ------ clamp_to_color
// Set color to red if any component is greater than one
RGBColor
Scene::clamp_to_color(const RGBColor& raw_color) const {
    RGBColor c(raw_color);

    if (raw_color.r > 1.0 || raw_color.g > 1.0 || raw_color.b > 1.0) {
        c.r = 1.0; c.g = 0.0; c.b = 0.0;
    }

    return (c);
}

// ------ display_pixel
// raw_color is the pixel color computed by the ray tracer
// its RGB floating point components can be arbitrarily large
// mapped_color has all components in the range [0, 1], but still floating point
// display color has integer components for computer display
// the Mac's components are in the range [0, 65535]
// a PC's components will probably be in the range [0, 255]
// the system-dependent code is in the function convert_to_display_color
// the function SetCPixel is a Mac OS function
void
Scene::display_pixel(const int row, const int column, const RGBColor& raw_color) const {
    RGBColor mapped_color;

    if (vp.show_out_of_gamut)
        mapped_color = clamp_to_color(raw_color);
    else
        mapped_color = max_to_one(raw_color);

    if (vp.gamma != 1.0)
        mapped_color = mapped_color.powc(vp.inv_gamma);

   //have to start from max y coordinate to convert to screen coordinates
   int x = column;
   int y = vp.vres - row - 1;

   render_res[row*vp.hres + column] = RGBColor((int)(mapped_color.r * 255),
                                               (int)(mapped_color.g * 255),
                                               (int)(mapped_color.b * 255));
}

// ------ hit_objects
ShadeRec
Scene::hit_objects(const Ray& ray) {
    ShadeRec sr(*this);
    double t;
    Normal normal;
    Point3D local_hit_point;
    float tmin = kHugeValue;
    int num_objects = objects.size();

    //cout << "hit_objects:" << num_objects << endl;
    for (int j = 0; j < num_objects; j++){
        if (objects[j]->hit(ray, t, sr) && (t < tmin)) {
            sr.hit_an_object	= true;
            tmin 				= t;
            sr.material_ptr     = objects[j]->get_material();
            sr.hit_point 		= ray.o + t * ray.d;
            normal 				= sr.normal;
            local_hit_point	 	= sr.local_hit_point;
        }
    }

    if(sr.hit_an_object) {
        sr.t = tmin;
        sr.normal = normal;
        sr.local_hit_point = local_hit_point;
        // i'm not quite sure
        //sr.material_ptr = sr.material_ptr->clone();
    }

    return(sr);
}


// ------ delete_objects
// Deletes the objects in the objects array, and erases the array.
// The objects array still exists, because it's an automatic variable, but it's empty
void
Scene::delete_objects(void) {
    int num_objects = objects.size();

    for (int j = 0; j < num_objects; j++) {
        delete objects[j];
        objects[j] = NULL;
    }

    objects.erase (objects.begin(), objects.end());
}


// ------ delete_lights
void
Scene::delete_lights(void) {
    int num_lights = lights.size();

    for (int j = 0; j < num_lights; j++) {
        delete lights[j];
        lights[j] = NULL;
    }

    lights.erase (lights.begin(), lights.end());
}

